package com.nervenets.general.redis;

import com.nervenets.general.Global;
import com.nervenets.general.exception.LogicException;
import com.nervenets.general.service.RedisService;
import com.nervenets.general.utils.HttpTools;
import com.nervenets.general.utils.SpringContextHolder;
import com.nervenets.general.utils.StringUtils;
import lombok.SneakyThrows;

import javax.servlet.http.HttpServletRequest;
import java.util.Vector;

/**
 * 跨方法全局分布式锁
 */
public final class RedisMultiPointLocker {
    private static final String LOCKED_KEY_PREFIX = "MultiLocker";
    private static final long LOCKED_TIMEOUT = 120000L;

    public static void lock(String key) {
        lock(key, 0);
    }

    public static void lock(String formatKey, Object... args) {
        lock(String.format(formatKey, args));
    }

    @SneakyThrows
    public static void lock(String key, int timeoutSeconds) {
        if (0 == timeoutSeconds) {
            HttpServletRequest httpRequest = HttpTools.getHttpRequest();
            if (null != httpRequest) {
                Object result = httpRequest.getAttribute(Global.Constants.SESSION_LOCK_KEYS);
                if (null == result) result = new Vector<String>();
                ((Vector<String>) result).add(key);
                httpRequest.setAttribute(Global.Constants.SESSION_LOCK_KEYS, result);
            }
        }

        String lockKey = StringUtils.formatKey(LOCKED_KEY_PREFIX, key);
        long startTime = System.currentTimeMillis();

        RedisService redisService = SpringContextHolder.getBean(RedisService.class);

        boolean canProcess = redisService.setNx(lockKey, lockKey);
        while (!canProcess) {
            if (System.currentTimeMillis() - startTime > LOCKED_TIMEOUT) {
                redisService.delete(lockKey);
                throw new LogicException("请求超时，请稍后再试");
            }
            canProcess = redisService.setNx(lockKey, lockKey);
            Thread.sleep(500);
        }
        if (timeoutSeconds > 0) {
            redisService.setExpire(lockKey, timeoutSeconds);
        }
    }

    public static boolean locked(String key) {
        String lockKey = StringUtils.formatKey(LOCKED_KEY_PREFIX, key);
        RedisService redisService = SpringContextHolder.getBean(RedisService.class);
        return redisService.hasKey(lockKey);
    }

    public static void unlock(String key) {
        String lockKey = StringUtils.formatKey(LOCKED_KEY_PREFIX, key);
        RedisService redisService = SpringContextHolder.getBean(RedisService.class);
        redisService.delete(lockKey);
    }

    public static void unlock(String formatKey, Object... args) {
        unlock(String.format(formatKey, args));
    }

    public static void lockAndConflict(String key) {
        lockAndConflict(key, 0);
    }

    public static void lockAndConflict(String key, int timeoutMs) {
        if (locked(key)) {
            if (timeoutMs > 0) {
                throw new LogicException("该操作每%s秒钟只可请求一次", timeoutMs);
            } else {
                throw new LogicException("请勿进行重复请求操作");
            }
        }
        lock(key, timeoutMs);
    }
}
